﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Diagnostics;

namespace Organized
{
    // A 3D heightmap terrain generated from a heightmap texture
    public class Terrain : Drawable3dComponent
    {

        public HeightMapData heightMapData;


        //the effect file
        Effect effect;

        //vertex array
        VertexNPMTexture[] vertices;
        //index array
        int[] indices;

        // vertexdeclaration
        VertexDeclaration myVertexDeclaration;

        // buffers
        VertexBuffer vertexBuffer;
        IndexBuffer indexBuffer;

        //texture used on terrain
      
        //multitextured
        Texture2D topTex;
        Texture2D botTex;


        private int numHVerts;
        private int numVVerts;

        //distance between each vertex
        int VertexSpacing;
      
        //height data
        float[,] heightData;
        Texture2D heightMap;
        private float minimumHeight;
        private float maximumHeight;

        public float textureScale;

        public int terrainWIDTH;
        public int terrainHEIGHT;

        float verticalscale = 600.0f;

        // skip pixels in heightmap for terrain smoothness
        int pixeldist;
        int indX;
        int indY;

        Camera camera;

        public bool WireMode = false;

        //stores an array of normals, to send to the heightmapdata object
        Vector3[,] normalData;

        BoundingBox boundingBox = new BoundingBox(new Vector3(-1), new Vector3(1));
        public Vector3 Position { get { return position; } set { position = value; } }
        public Vector3 EulerRotation
        {
            get { return MathUtil.MatrixToVector3(Rotation); }
            set { this.Rotation = MathUtil.Vector3ToMatrix(value); }
        }

        public Matrix Rotation { get { return rotation; } set { rotation = value; } }
        public Vector3 Scale { get { return scale; } set { scale = value; } }
        public BoundingBox BoundingBox { get { return boundingBox; } }


        public virtual Matrix WorldMatrix
        {
            get { return worldMatrix; }
            set { worldMatrix = value; }
        }
        Matrix worldMatrix = Matrix.Identity;

        //vertex that holds normal, position, and multiple textures

        public struct VertexNPMTexture
        {
            public Vector3 Position;
            public Vector3 Normal;
            public Vector4 TextureCoordinate;
           // public Vector4 TexWeights;

            public static int SizeInBytes = (3 + 3 + 4 ) * sizeof(float);

           // public VertexNPMTexture(Vector3 pos, Vector3 nor, Vector4 texc, Vector4 texw)
            public VertexNPMTexture(Vector3 pos, Vector3 nor, Vector4 texc )
            {
                Position = pos;
                Normal = nor;
                TextureCoordinate = texc;
               
            }


            public static VertexElement[] VertexElements = new VertexElement[]
             {
                 new VertexElement( 0, 0, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Position, 0 ),
                 new VertexElement( 0, sizeof(float) * 3, VertexElementFormat.Vector3, VertexElementMethod.Default, VertexElementUsage.Normal, 0 ),
                 new VertexElement( 0, sizeof(float) * 6, VertexElementFormat.Vector4, VertexElementMethod.Default, VertexElementUsage.TextureCoordinate, 0 )
              
             };
        }


        // I3DComponent values
        Vector3 indexPosition = Vector3.Zero;
        Vector3 position = Vector3.Zero;
        Matrix rotation = Matrix.Identity;
        Vector3 scale = new Vector3(1, 1, -1);


        public float lightFactor = 1f;


        //Constructor
        public Terrain(int _pixeldist, int _vertexspace, Lighting _light, float lightfactor, Vector3 _position, Texture2D HeightMap, Texture2D _topTex,  float TexScale, GameScreen Parent)
            : base(_light, _position, Parent)
        {
            textureScale = TexScale;
            lightFactor = lightfactor;
            //get heightmaps width and height
            pixeldist = _pixeldist;
           // pixeldist = 10;
            VertexSpacing = _vertexspace;
            Trace.WriteLine("Vertex spacing " + VertexSpacing);
            numVVerts = (HeightMap.Width / pixeldist);
            numHVerts = (HeightMap.Width / pixeldist);

            terrainWIDTH = numHVerts * VertexSpacing;
            terrainHEIGHT = numHVerts * VertexSpacing;
            Trace.WriteLine("num V Verts " + numVVerts);
            Trace.WriteLine("total verts:" + (numVVerts * numVVerts));
            //create heightdata for every pixel of the bitmap
            heightMap = HeightMap;
            createHeightData();

            //copy textures 
            topTex = _topTex;
           


            //setup effect
            effect = Engine.Content.Load<Effect>("Content/Effects/modelShader");

            // build terrain
            SetUpVertices();
            SetUpIndices();
            CalculateNormals();
            SetBuffers();

            //after terrain is build, create a heightmapdata object
            heightMapData = new HeightMapData(heightData, normalData, VertexSpacing);
            camera = Engine.Services.GetService<Camera>();

        }


        public void setTextures(Texture2D _topTex, Texture2D _botTex)
        {
            topTex = _topTex;
            botTex = _botTex;
        }


        private void createHeightData()
        {
            int width = heightMap.Width;
            int height = heightMap.Height;

            // Get data from heightmap
            Color[] heightMapColors = new Color[width * height];
            heightMap.GetData<Color>(heightMapColors);

            // Setup height data from heightmap data

            heightData = new float[width/pixeldist, height/pixeldist];

            for (int x = 0; x < width/pixeldist; x++)
                for (int y = 0; y < height/pixeldist; y++)
                {
                    heightData[x, y] = heightMapColors[(x * pixeldist) + (y * pixeldist) * width].R;

                    if (heightData[x, y] < minimumHeight)
                        minimumHeight = heightData[x, y];
                    if (heightData[x, y] > maximumHeight)
                        maximumHeight = heightData[x, y];
                }

            for (int x = 0; x < width/pixeldist; x++)
                for (int y = 0; y < height/pixeldist; y++)
                    heightData[x, y] = (heightData[x, y] - minimumHeight)
                        / (maximumHeight - minimumHeight) * verticalscale;

            
            //set height according to reference on heightData
            indX = (heightMap.Width / numHVerts);
            indY = (heightMap.Height / numVVerts);
           
        }



        private void SetUpVertices()
        {
            vertices = new VertexNPMTexture[numVVerts * numHVerts];



            for (int x = 0; x < numHVerts; x++)
            {
                for (int y = 0; y < numVVerts ; y++)
                {



                    //if x = hverts, vertice.x should get heightdata(heightmap.width)
                    //so, get wich segment of total.
                    //then multiply it with width/segmetns
                    //the length of a segment when we devide the width of heightmap into 

                    vertices[x + y * numHVerts].Position = new Vector3(x * VertexSpacing, heightData[x, y], y * VertexSpacing);

                    vertices[x + y * numHVerts].TextureCoordinate.X = (float)x / textureScale;
                    vertices[x + y * numHVerts].TextureCoordinate.Y = (float)y / textureScale;


                   // vertices[x + y * numHVerts].TexWeights.Y = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - minimumHeight) / (verticalscale/2), 0, 1);
                   // vertices[x + y * numHVerts].TexWeights.X = MathHelper.Clamp(1.0f - Math.Abs(heightData[x, y] - maximumHeight) / (verticalscale/2), 0, 1);


                   // float total = vertices[x + y * numHVerts].TexWeights.X;
                   // total += vertices[x + y * numHVerts].TexWeights.Y;
                    //vertices[x + y * numHVerts].TexWeights.X = 1;
                    // vertices[x + y * numHVerts].TexWeights.Y = 0;

                   // vertices[x + y * numHVerts].TexWeights.X /= total;
                   // vertices[x + y * numHVerts].TexWeights.Y /= total;

                }
            }


            myVertexDeclaration = new VertexDeclaration(Engine.GraphicsDevice, VertexNPMTexture.VertexElements);

        }


        private void SetUpIndices()
        {
            indices = new int[(numHVerts - 1) * (numVVerts - 1) * 6];
            int counter = 0;

            for (int y = 0; y < numVVerts - 1; y++)
            {
                for (int x = 0; x < numHVerts - 1; x++)
                {
                    int lowerLeft = x + y * numHVerts;
                    int lowerRight = (x + 1) + y * numHVerts;
                    int topLeft = x + (y + 1) * numHVerts;
                    int topRight = (x + 1) + (y + 1) * numHVerts;

                    indices[counter++] = topLeft;
                    indices[counter++] = lowerRight;
                    indices[counter++] = lowerLeft;

                    indices[counter++] = topLeft;
                    indices[counter++] = topRight;
                    indices[counter++] = lowerRight;
                }
            }


        }

        private void CalculateNormals()
        {
            for (int i = 0; i < vertices.Length; i++)
            {
                vertices[i].Normal = new Vector3(0, 0, 0);
            }


            for (int i = 0; i < indices.Length / 3; i++)
            {
                int index1 = indices[i * 3];
                int index2 = indices[i * 3 + 1];
                int index3 = indices[i * 3 + 2];

                Vector3 side1 = vertices[index1].Position - vertices[index3].Position;
                Vector3 side2 = vertices[index1].Position - vertices[index2].Position;
                Vector3 normal = Vector3.Cross(side1, side2);


                vertices[index1].Normal += normal;
                vertices[index2].Normal += normal;
                vertices[index3].Normal += normal;   
            



            }

            

            for (int i = 0; i < vertices.Length; i++)
            {
                //normalize
                vertices[i].Normal.Normalize();
               
               
            }

            normalData = new Vector3[numHVerts, numVVerts];
             //store the normal in the normal array           
             for(int x= 0; x < numHVerts; x++){
                 for(int y = 0; y < numVVerts; y++){
                     normalData[x, y] = vertices[x + y * numHVerts].Normal;                    
                 }
             }

        }

        private void SetBuffers()
        {
            vertexBuffer = new VertexBuffer(Engine.GraphicsDevice, vertices.Length * VertexNPMTexture.SizeInBytes, BufferUsage.WriteOnly);
            vertexBuffer.SetData(vertices);

            indexBuffer = new IndexBuffer(Engine.GraphicsDevice, typeof(int), indices.Length, BufferUsage.WriteOnly);
            indexBuffer.SetData(indices);
        }



        public override void DrawDepthMap()
        {
           
            SetupEffect("DepthMap");
            drawTerrain();

        }

        public override void Draw()
        {
           
            //add me to the component render list, for postproceccing a depthmap
            this.Parent.DrawableRenderList.Add(this);

            SetupEffect("Textured_2_0");
            drawTerrain();
        }

        public void SetupEffect(string technique)
        {

            WorldMatrix = Matrix.CreateTranslation(Position); //* Matrix.CreateTranslation(-terrainWidth / 2.0f, 0, terrainHeight / 2.0f);

  
            effect.CurrentTechnique = effect.Techniques[technique];
            if (technique == "Textured_2_0")
            {
              
                effect.Parameters["xTexture"].SetValue(topTex);
                effect.Parameters["xLightFactor"].SetValue(lightFactor);
            }

            effect.Parameters["xView"].SetValue(camera.View);
            effect.Parameters["xProjection"].SetValue(camera.Projection);
            effect.Parameters["xWorld"].SetValue(WorldMatrix);
            effect.Parameters["xEnableLighting"].SetValue(true);

            
            effect.Parameters["xLightDirection"].SetValue(lighting.lightDirection);
            effect.Parameters["xAmbient"].SetValue(lighting.ambientPower);
            
            
        }


        public void drawTerrain()
        {

            //VertexDeclaration tmpDecl = Engine.GraphicsDevice.VertexDeclaration;
            Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullClockwiseFace;
            if (WireMode)
            {
                Engine.GraphicsDevice.RenderState.FillMode = FillMode.WireFrame;
            }
            Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
            Engine.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;

           
            //Engine.GraphicsDevice.RenderState.CullMode = CullMode.None;

            effect.Begin();
            foreach (EffectPass pass in effect.CurrentTechnique.Passes)
            {
                pass.Begin();
                //draw from buffers
                Engine.GraphicsDevice.VertexDeclaration = myVertexDeclaration;
                Engine.GraphicsDevice.Indices = indexBuffer;
                Engine.GraphicsDevice.Vertices[0].SetSource(vertexBuffer, 0, VertexNPMTexture.SizeInBytes);
                //Engine.GraphicsDevice.DrawUserIndexedPrimitives(PrimitiveType.TriangleList, vertices, 0, vertices.Length, indices, 0, indices.Length / 3);
                Engine.GraphicsDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, vertices.Length, 0, indices.Length / 3);
                pass.End();
            }
            effect.End();
           // Engine.GraphicsDevice.VertexDeclaration = tmpDecl;
            // Set the vertex winding back
           // Engine.GraphicsDevice.RenderState.CullMode = CullMode.CullCounterClockwiseFace;
            Engine.GraphicsDevice.RenderState.FillMode = FillMode.Solid;

            //Engine.GraphicsDevice.RenderState.DepthBufferEnable = true;
            //Engine.GraphicsDevice.RenderState.DepthBufferWriteEnable = true;
            Engine.GraphicsDevice.RenderState.CullMode = CullMode.None;
            
        }

    }


}